﻿using Microsoft.Phone;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;
using ZaloCore.Log;
using ZaloCore.utils;
using System.IO.IsolatedStorage;
using System.Threading;
using System.Windows;
using Zalo.Download;
using ImageTools.IO.Png;
using ImageTools.IO;
using ImageTools;
using ImageTools.Filtering;

namespace InstaMe
{
    public class ImageCache
    {
        public const byte NORMAL_CACHE = 1;
        public const byte SHORT_CACHE = 2;

        private static long MAX_MEM_CACHE_SIZE = DevicesUtils.MAX_MEMORY_USED() * 20 / 100;  // 20% of Mem ~ 40Mb
        private const int MAX_PIXEL_CACHE = 640;        
        private const long VALID_TIME = 7 * 86400000;  // 7Days
        public const long MAX_CACHE_TIME = long.MaxValue;
        public const long SHORT_CACHE_TIME = 5000;  // 30s

        private long mMemCacheSize;
        private HashVector<string, CacheObject> mMemCache;

        private static ImageCache mInstance = null;
        private static object mLock = new object();

        private ImageCache()
        {
            mMemCacheSize = 0;
            mMemCache = new HashVector<string, CacheObject>();
        }

        public static ImageCache getInstance()
        {
            if (mInstance == null)
            {
                lock (mLock)
                {
                    if (mInstance == null)
                    {
                        mInstance = new ImageCache();
                    }
                }
            }
            return mInstance;
        }

        public void Put(string aImageUrl, BitmapImage aImageSource, byte aCacheType)
        {
            if (String.IsNullOrEmpty(aImageUrl) || aImageSource == null)
            {
                return;
            }
            try
            {
                long iCurrentTime = TimeUtils.currentTimeMilis();
                long iMemSize = 0;
                using (AutoResetEvent iResetEvent = new AutoResetEvent(false))
                {
                    Deployment.Current.Dispatcher.BeginInvoke(() =>
                    {                        
                        if (aImageSource.PixelHeight > MAX_PIXEL_CACHE || aImageSource.PixelWidth > MAX_PIXEL_CACHE)
                        {
                            int iScaleW;
                            int iScaleH;
                            if (aImageSource.PixelWidth > aImageSource.PixelHeight)
                            {
                                iScaleW = MAX_PIXEL_CACHE;
                                iScaleH = aImageSource.PixelHeight * MAX_PIXEL_CACHE / aImageSource.PixelWidth;
                            }
                            else
                            {
                                iScaleH = MAX_PIXEL_CACHE;
                                iScaleW = aImageSource.PixelWidth * MAX_PIXEL_CACHE / aImageSource.PixelHeight;
                            }
                            // Scale
                            try {
                                WriteableBitmap wBitmap = new WriteableBitmap(aImageSource);
                                ExtendedImage exImage = wBitmap.ToImage();
                                exImage = ExtendedImage.Resize(exImage, iScaleW, iScaleH, new NearestNeighborResizer());
                                aImageSource.UriSource = null;
                                aImageSource.SetSource(exImage.ToStream());
                            } catch (Exception ex) {
                            }
                        }
                        iMemSize = aImageSource.PixelWidth * aImageSource.PixelHeight * 4;
                        iResetEvent.Set();
                    });
                    iResetEvent.WaitOne();
                }
                // It will roll random here
                // Put To Memcache
                CacheObject iCacheItem = new CacheObject(aImageSource, iCurrentTime, iMemSize);
                if (aCacheType == SHORT_CACHE)
                {
                    iCacheItem.setExpiredTime(iCurrentTime + SHORT_CACHE_TIME);
                }
                AddMemCache(aImageUrl, iCacheItem);
                // Remove Top Cache if memcahce overload
                BalanceMemCache();
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
            }
        }

        public long MemCacheSize()
        {
            return mMemCacheSize;
        }

        public BitmapImage GetMemCache(string aFileUrl, ref bool IsExpired)
        {
            lock (mMemCache)
            {
                if (mMemCache.ContainsKey(aFileUrl))
                {
                    // Swap mem cache
                    CacheObject iTempCacheObj = mMemCache.GetValue(aFileUrl);
                    if (iTempCacheObj != null)
                    {
                        long iCurrentTime = TimeUtils.currentTimeMilis();
                        // check Expired memcache
                        if (iCurrentTime - iTempCacheObj.getExpiredTime() > 0)
                        {
                            IsExpired = true;
                        }
                        // Swap cache
                        // Remove
                        mMemCache.Remove(aFileUrl);
                        // Add to Bottom
                        mMemCache.Add(aFileUrl, iTempCacheObj);
                        return iTempCacheObj.getImage();               
                    }
                }
                return null;
            }            
        }

        public BitmapImage Get(string aFileUrl, DownloadManager.DownloadType aDownloadType)
        {
            return Get(aFileUrl, aDownloadType, true);
        }

        public BitmapImage Get(string aFileUrl, DownloadManager.DownloadType aDownloadType, bool isCache)
        {
            if (String.IsNullOrEmpty(aFileUrl))
            {
                return null;
            }
            // Get From mem Cache            
            bool isCacheExpired = false;
            BitmapImage iCacheImage = GetMemCache(aFileUrl, ref isCacheExpired);
            if (iCacheImage != null && !isCacheExpired)
            {
                return iCacheImage;
            }

            BitmapImage iNewImage = null;

            // Local File
            // Read File
            byte[] iImageData = null;
            if (aFileUrl.Contains('\\')) // Local file
            {
                iImageData = FileUtils.ReadDataFromFile(aFileUrl);
            }
            else if (aFileUrl.Contains('/')) // resource
            {
                iImageData = FileUtils.ReadDataFromResource(FileUtils.ToResouceUrl(aFileUrl));
            }

            if (iImageData != null)
            {
                using (MemoryStream iImageStream = new MemoryStream(iImageData))
                {
                    // Create Bitmap Image
                    using (AutoResetEvent iResetEvent = new AutoResetEvent(false))
                    {
                        Deployment.Current.Dispatcher.BeginInvoke(() =>
                        {
                            try
                            {
                                iNewImage = new BitmapImage();
                                iNewImage.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
                                iNewImage.SetSource(iImageStream);
                            }
                            catch (Exception ex)
                            {
                                Logger.LogException(ex);
                            }
                            iResetEvent.Set();
                        });
                        iResetEvent.WaitOne();
                    }
                    // Put again to MemCache
                    if (isCache)
                    {
                        Put(aFileUrl, iNewImage, NORMAL_CACHE);
                    }
                }
            }
            return iNewImage;
        }

        private void BalanceMemCache()
        {
            lock (mMemCache)
            {
                // Remove Cache Object Utils mMemCacheSize < MAX_MEM_CACHE_SIZE
                // If just has one item in cache then leave it there
                while (mMemCache.Size() > 1 && (mMemCacheSize > MAX_MEM_CACHE_SIZE))
                {
                    CacheObject iCacheObj = mMemCache.ValueAt(0);
                    if (iCacheObj != null)
                    {
                        long iObjMemSize = iCacheObj.getMemSize();
                        mMemCacheSize -= iObjMemSize;
                        if (mMemCacheSize < 0)
                        {
                            mMemCacheSize = 0;
                        }
                        // Reset BitmapImage Source
                        iCacheObj.releaseImage();
                        mMemCache.RemoveAt(0);
                        Logger.LogSonNT("========= Roll Cache: " + TimeUtils.timeToString(iCacheObj.getLastAccessTime(), "HH:MM:ss:fff"));
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }

        private void PrepareMemCache(long aIncresSize)
        {
            lock (mMemCache)
            {
                // Remove Cache Object Utils mMemCacheSize + aIncresSize < MAX_MEM_CACHE_SIZE
                while (mMemCache.Size() > 0 && (mMemCacheSize + aIncresSize > MAX_MEM_CACHE_SIZE))
                {
                    CacheObject iCacheObj = mMemCache.ValueAt(0);
                    if (iCacheObj != null)
                    {
                        long iObjMemSize = iCacheObj.getMemSize();
                        mMemCacheSize -= iObjMemSize;
                        if (mMemCacheSize < 0)
                        {
                            mMemCacheSize = 0;
                        }
                        // Reset BitmapImage Source
                        iCacheObj.releaseImage();
                        mMemCache.RemoveAt(0);
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }

        private void AddMemCache(string aFileUrl, CacheObject aCacheObject)
        {
            lock (mMemCache)
            {
                if (mMemCache.ContainsKey(aFileUrl))
                {
                    CacheObject iTempCacheObj = mMemCache.GetValue(aFileUrl);
                    if (iTempCacheObj != null)
                    {
                        mMemCacheSize -= iTempCacheObj.getMemSize();
                    }
                    mMemCache.Remove(aFileUrl);
                    iTempCacheObj.releaseImage();
                }
                mMemCache.Add(aFileUrl, aCacheObject);
                mMemCacheSize += aCacheObject.getMemSize();
            }
        }        

/*********************************************************************************/
/* Cache Object
/*********************************************************************************/
        private class CacheObject
        {            
            private BitmapImage mImage;
            private long mLastAccessTime;
            private long mMemSize;
            private long mExpiredTime = MAX_CACHE_TIME;

            public CacheObject(BitmapImage aImage, long aLastAccessTime, long aMemSize)
            {
                mImage = aImage;
                mLastAccessTime = aLastAccessTime;
                mMemSize = aMemSize;
            }

            public void setExpiredTime(long aCacheTime)
            {
                mExpiredTime = aCacheTime;
            }

            public long getExpiredTime()
            {
                return mExpiredTime;
            }

            public BitmapImage getImage()
            {
                return mImage;
            }

            public void releaseImage()
            {
                Deployment.Current.Dispatcher.BeginInvoke(() =>
                {
                    if (mImage != null)
                    {
                        mImage.UriSource = null;
                    }
                });                                
            }

            public long getLastAccessTime()
            {
                return mLastAccessTime;
            }

            public long getMemSize()
            {
                return mMemSize;
            }
        }
    }
}
